package cn.bbstone.pisces2.ui.handlers;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.inject.Inject;
import javax.inject.Named;

import org.eclipse.e4.core.di.annotations.CanExecute;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.di.UIEventTopic;
import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.services.IServiceConstants;
import org.eclipse.e4.ui.workbench.IWorkbench;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;
import org.osgi.service.prefs.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.joran.action.ActionUtil.Scope;
import cn.bbstone.pisces2.client.ClientStarter;
import cn.bbstone.pisces2.cmm.Pi2ContextUtil;
import cn.bbstone.pisces2.cmm.RunModeEnum;
import cn.bbstone.pisces2.cmm.UIConst;
import cn.bbstone.pisces2.config.ConfigModel;
import cn.bbstone.pisces2.listener.IListenerRegister;
import cn.bbstone.pisces2.listener.OpEnum;
import cn.bbstone.pisces2.server.ServerStarter;
import cn.bbstone.pisces2.ui.base.enums.CmdEnum;
import cn.bbstone.pisces2.ui.base.notify.Pi2Events;
import cn.bbstone.pisces2.ui.base.notify.Pi2ListenerRegister;
import cn.bbstone.pisces2.ui.base.util.PreferenceUtil;
import cn.bbstone.pisces2.ui.job.JobService;
import cn.bbstone.pisces2.ui.preference.FieldInfoEnum;
import cn.bbstone.pisces2.util.FLIUtil;

public class StartupHandler {

	private static Logger log = LoggerFactory.getLogger(StartupHandler.class);

	@Inject
	IEventBroker eventBroker;

	@Inject
	IListenerRegister listenerRegister;

	@Inject
	private UISynchronize sync;

	@Inject
	private JobService jobService;

	private Shell shell;

	private boolean canExecuted = true;

	private void disabledButton() {
		this.canExecuted = false;
	}

	private void enabledButton() {
		this.canExecuted = true;
	}

	@CanExecute
	public boolean canExecute() {
//		log.info("start-canExecuted: {}", this.canExecuted);
		return canExecuted;
	}

	@Execute
	public void execute(IWorkbench workbench, @Named(IServiceConstants.ACTIVE_SHELL) Shell shell) {
		this.disabledButton();
		eventBroker.post(Pi2Events.STARTUP, CmdEnum.startup);
		this.shell = shell;

//		System.out.println((this.getClass().getSimpleName() + " called"));
		log.info((this.getClass().getSimpleName() + " called")); //$NON-NLS-1$

		// set eventBroker on startup only once
		((Pi2ListenerRegister) listenerRegister).setEventBroker(eventBroker);

		ConfigModel configModel = PreferenceUtil.getConfigModel();

		// runAs client
		if (UIConst.RUN_AS_SERVER.equals(Pi2ContextUtil.getRunAs())) {
			if (Pi2ContextUtil.isRunning()) {
				MessageDialog.openConfirm(shell, Messages.dialog_title_confirm,
						Messages.dialog_content_startup_server_stated_conflict);
				return;

			}
			eventBroker.post(Pi2Events.OPERATION, OpEnum.s2_loading_server.getDisplayText());
			// server always rebuild/overwrite fli.idx, directly startup
			jobService.newJob(sync, "server-startup", () -> { //$NON-NLS-1$
				log.info("server starting@{}:{}...", configModel.getServerHost(), configModel.getServerPort()); //$NON-NLS-1$
				ServerStarter.startup(configModel, listenerRegister); // will block thread
			}, () -> { // will never run this because thread blocked by FileServer.startup() method
				// job will not run here... why?(because callback defined run after task, and
				// task blocked.

				log.info("enable/disable UI menu/buttons, update status, etc.");
				// enable/disable UI menu/buttons, update status, etc.

			});

		}
		// runAs client
		if (UIConst.RUN_AS_CLIENT.equals(Pi2ContextUtil.getRunAs())) {
			if (Pi2ContextUtil.isRunning()) {
				MessageDialog.openConfirm(shell, Messages.dialog_title_confirm,
						Messages.dialog_content_startup_client_stated_conflict);
				return;
			}
			eventBroker.post(Pi2Events.OPERATION, OpEnum.s2_loading_client.getDisplayText());

			switch (startMode()) {
			case NEW:
//				resetPosCounter();
				if (pingServerReachable(configModel)) {
					// TODO startup server in another thread, or will be block
					jobService.newJob(sync, "client-startup-new", () -> { //$NON-NLS-1$
						log.info("client startup with NEW mode..."); //$NON-NLS-1$
						Pi2ContextUtil.setRunMode(RunModeEnum.NEW);
						ClientStarter.startup(configModel, listenerRegister); // will block thread
					}, () -> { // will never run this because thread blocked by FileServer.startup() method
						// TODO job will not run here... why?

						log.info("enable/disable UI menu/buttons, update status, etc."); //$NON-NLS-1$
						// enable/disable UI menu/buttons, update status, etc.

					});

				}
				// if server startup successfully, update statusbar operation msg
//				eventBroker.post(Events.OPERATION, OperationEnum.s4_done_startup_server.getDisplayText());
				break;
			case RERUN:
//				resetPosCounter();
				if (MessageDialog.openConfirm(shell, Messages.dialog_title_confirm,
						Messages.dialog_content_return_with_notempty_client_root)) {
					if (pingServerReachable(configModel)) {
						jobService.newJob(sync, "client-startup-rerun", () -> { //$NON-NLS-1$
							log.info("client startup with RERUN mode..."); //$NON-NLS-1$
							Pi2ContextUtil.setRunMode(RunModeEnum.RERUN);
							// clean ~/.fli_client directory first
							if (clearClientBizMetaInfo()) {
								ClientStarter.startup(configModel, listenerRegister);
							}
						}, () -> {
							// enable/disable UI menu/buttons, update status, etc.

						});
					}
				}
				break;
			default:

			}
		}

		// check if savepoint file & fli.idx file exists

		// if exists(sp & idx)

		// if run complete
		// show MessageDialog, whether want to run it again?
		// if true, start server to run
		// if false, close dialog, update status bar

		// if not complete,
		// show dialog, display current progress, whether continue?
		// if true, start server continue running
		// if false, close dialog, update status bar

		// if non-exists(sp & idx)
		// start server to run, and update status bar

	}

	// TODO: implement it
	private boolean pingServerReachable(ConfigModel configModel) {
//		IEclipsePreferences preferences = PreferenceUtil.getPreference();
//		String host = preferences.get(FieldInfoEnum.SERVER_HOST.getPreferenceName(), "server_host");

//		ConfigModel serverConfigModel = ConfigFactory.loadConfigs(Const.SERVER);
//		String host = serverConfigModel.getServerHost();
		
		String host = configModel.getServerHost();
		int port = configModel.getServerPort();
		
		int timeOutMillis = 5000;
		try (Socket socket = new Socket()) {
			socket.connect(new InetSocketAddress(host, port), timeOutMillis);
	        return true;
	    } catch (IOException ex) {
	        return false;
	    }
		
//		try {
//			InetAddress inetAddress = InetAddress.getByName(host);
//			boolean isReachable = inetAddress.isReachable(5000); // 5sec.
//			if (!isReachable) {
//				log.error("server({}) is NOT reachable?", host);
//			}
//			return isReachable;
//		} catch (IOException e) {
//			String msg =  e.getMessage();
//			log.error("ping server({}) error: {}", host, msg);
//		}
//		return false;
	}

	private boolean clearClientBizMetaInfo() {
//		Preferences preferences = PreferenceUtil.getPreference();
//		String clientRoot = preferences.get(FieldInfoEnum.CLIENT_ROOT.getPreferenceName(), null);
		String clientMetaRoot = FLIUtil.getClientIdxPathStr();
		try {
			File file = Paths.get(clientMetaRoot).toFile();
			delDir(file);
//			Files.deleteIfExists(Paths.get(clientRoot));
			return true;
		} catch (Exception e) {
			log.error("error occurs when clear client biz meta info dirctory.", e); //$NON-NLS-1$
		}
		return false;
	}

	public void delDir(File root) throws Exception {
		File[] files = root.listFiles();
		if (files != null) {
			for (File file : files) {
				if (file.isDirectory()) {
					delDir(file);
				} else {
					file.delete();
				}
			}
		}
		// do not delete root directory
		root.delete();
	}

	/**
	 * TODO only check clientRoot whether empty, if empty: NEW if not empty: RERUN,
	 * clear all, and fresh start
	 * 
	 * @return
	 */
	private RunModeEnum startMode() {
		RunModeEnum runModeEnum = RunModeEnum.NEW;
		Preferences preferences = PreferenceUtil.getPreference();
		String clientRoot = preferences.get(FieldInfoEnum.CLIENT_ROOT.getPreferenceName(), null);
		// check clientRoot whether exists
		if (clientRoot != null && Files.exists(Paths.get(clientRoot))) {
			// check directory
			if (Files.isDirectory(Paths.get(clientRoot))) {
				File dir = Paths.get(clientRoot).toFile();
				// directory has any files
				if (dir.listFiles().length > 0) {
					runModeEnum = RunModeEnum.RERUN;
				}
			}
		}
		Pi2ContextUtil.setRunMode(runModeEnum);
		return runModeEnum;
	}

	/**
	 * if currentPos.get() >= fliSavePoint.getCount() true, mean all file received,
	 * so, need to reset currentPos and sp.count
	 * 
	 * @return
	 */
//	private boolean resetPosCounter() {
//		boolean bool = false;
//		// reset cache current pos
//		ClientFliIndexCache.resetCurrentPos();
//		// reset save point count
//		FliSavePoint fliSavePointDisk = FLIUtil.readClientSavePoint();
//        fliSavePointDisk.setFileNo(-1L);
//        fliSavePointDisk.setUpdateTime(System.currentTimeMillis());
//        FLIUtil.writeClientSavePoint(fliSavePointDisk);
//		
//		return bool;
//	}

	// -------------------------------- listening events --------------
	/**
	 * FileServer started successfully
	 * 
	 * @param cmdEnum
	 */
	@Inject
	@Optional
	void onServerStarted(final @UIEventTopic(Pi2Events.SERVER_STARTUP_HANDLER) CmdEnum cmdEnum) {
		if (CmdEnum.startup.equals(cmdEnum)) {
			Pi2ContextUtil.setRunning(true);
			showServerStartedInfoDialog();
		}
	}

	@Inject
	@Optional
	void onClientStarted(final @UIEventTopic(Pi2Events.CLIENT_STARTUP_HANDLER) CmdEnum cmdEnum) {
		if (CmdEnum.startup.equals(cmdEnum)) {
			Pi2ContextUtil.setRunning(true);
			showClientStartedInfoDialog();
		}
	}

	@Inject
	@Optional
	void onClientConnected(final @UIEventTopic(Pi2Events.CLIENT_CONNECTED_HANDLER) CmdEnum cmdEnum) {
		Pi2ContextUtil.setClientConnected(true);
	}

	private void showServerStartedInfoDialog() {
		MessageDialog.openInformation(shell, Messages.dialog_title_server_started,
				Messages.dialog_content_server_started);
	}

	private void showClientStartedInfoDialog() {
		MessageDialog.openInformation(shell, Messages.dialog_title_client_started,
				Messages.dialog_content_client_started);
	}

	@Inject
	@Optional
	void onStop(final @UIEventTopic(Pi2Events.SHUTDOWN) CmdEnum cmdEnum) {
		// when stop pi2(server/client), start button enabled
		if (CmdEnum.stop.equals(cmdEnum)) {
			this.enabledButton();
		}

	}

}
